/**
 * 
 */
package multidomain;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Vector;
import controlPlane.OBGPSpeaker;
import tracing.OBGPTracer;
import util.Auxiliar;
import util.Dijkstra;
import util.WeightedGraph;
import wdmsim.Flow;
import wdmsim.Lightpath;
import wdmsim.Node;
import wdmsim.OXC;
import wdmsim.PhysicalTopology;
import wdmsim.WDMLink;

/**
 *  Route Information Base (RIB)
 * @author alissonslp
 *
 */
public class RIB {

	PhysicalTopology pt = PhysicalTopology.getInstance();
	HashMap<OXC,TableRow> multidomainTable;
	int id;
	
	public static class TableRow implements Cloneable {
		PhysicalTopology pt = PhysicalTopology.getInstance();
		private int[] asPath;
		private int nextHop;
		private int wavelength;
		private Vector<Integer> freeWavelengths;
		private Vector<Integer> optimalWavelengthsPath;
		
		public TableRow() {			
		}
		
		public TableRow(int[] path, int nextHop, int wavelength) {
			this.asPath = path;
			this.nextHop = nextHop;
			this.wavelength = wavelength;
		}
		
		public TableRow(int[] path, int nextHop, int wavelength, Vector<Integer> freeWavelengths) {
			this.asPath = path;
			this.nextHop = nextHop;
			this.wavelength = wavelength;
			this.freeWavelengths = (Vector<Integer>)freeWavelengths.clone();
		}
		
		/*public TableRow(int[] path, int nextHop, Vector<Integer> optimalPath) {
			this.asPath = path;
			this.nextHop = nextHop;
			this.optimalWavelengthsPath = (Vector<Integer>)optimalPath.clone();
		}*/

		public int[] getPath() {
			return this.asPath;
		}
		
		public int getPath(int index) {
			return this.asPath[index];
		}
		
		public int getNextHop() {
			return this.nextHop;
		}
		
		public int getWavelength() {
			return this.wavelength;
		}
		
		public Vector<Integer> getFreeWavelengths() {
			return this.freeWavelengths;
		}

		public void setAsPath(int[] asPath) {
			this.asPath = asPath;
		}

		public void setNextHop(int nextHop) {
			this.nextHop = nextHop;
		}

		public void setWavelength(int wavelength) {
			this.wavelength = wavelength;
		}

		public void setFreeWavelengths(Vector<Integer> freeWavelengths) {
			this.freeWavelengths = (Vector<Integer>)freeWavelengths.clone();
		}
		
		public Vector<Integer> getOptimalWavelengthsPath() {
			return optimalWavelengthsPath;
		}

		public void setOptimalWavelengthsPath(Vector<Integer> optimalWavelengthsPath) {
			this.optimalWavelengthsPath = optimalWavelengthsPath;
		}

	}
	
	public RIB(int id) throws Exception {
		this.id = id;
		multidomainTable = new HashMap<OXC, TableRow>();
		generateMultidomainInformationBase();
	}
	
	/**
	 * @param src
	 */
	public int[] getPath(Domain domain) {
		return multidomainTable.get(domain).getPath();
	}
	
	public TableRow getTableRow(Node node) {
		return multidomainTable.get(node);
	}

	/**
	 * @throws Exception 
	 * 
	 */
	private void generateMultidomainInformationBase() throws Exception {
		PhysicalTopology pt = PhysicalTopology.getInstance();
		OXC[] nodesVector = pt.getNodeVector();
		WeightedGraph domainGraph = pt.getDomainWeightedGraph();
		WeightedGraph graph = pt.getWeightedGraph();
		Vector<Integer> freeWavelengths = null;
		Vector<Integer> maxWavelengthsBandWidth = null;
		Vector<Integer> optimalPath = null;
		
		for(int d=0; d<pt.getNumNodes(); d++) {
			OXC dst = nodesVector[d];
			if(!pt.belongsSameDomain(this.id, dst.getId())) {
				int[] domainPath = Dijkstra.getShortestHopPath(domainGraph, pt.getDomainFromNode(this.id).getID(), pt.getDomainFromNode(dst.getId()).getID());
				int[] path = null;
				do {
					path = Dijkstra.getShortestHopPath(graph, this.id, dst.getId());
					if(path!=null && path.length>0 && !pt.isPathEquivalentMultidomainPath(domainPath, path)) {
						int dp[] = pt.getDomainPathFromPath(path);
						int index = Auxiliar.findWrongDomainNode(domainPath, dp);
						WDMLink link = pt.getInterdomainLink(dp[index-1], dp[index]);
						graph.removeEdge(link.getSrcId(), link.getDstId());
					}
				} while(path!=null && path.length>0 && !pt.isPathEquivalentMultidomainPath(domainPath, path));
				
				/*if(path!=null && path.length>0) {
					freeWavelengths = Auxiliar.array2Vector(pt.getPathFreeWavelengths(path));
				}
				if(freeWavelengths!=null && freeWavelengths.size()>0) {
					int thisDomain = pt.getDomainFromNode(this.id).getID();
					//pt.getInBorderNode(neighborDomainId, dst);
					WDMLink wdmLink = pt.getInterdomainLink(thisDomain, domainPath[1]);
					TableRow tableRow = new TableRow(domainPath, wdmLink.getDstId(), freeWavelengths.get((int)(Math.random() * ((freeWavelengths.size()-1) + 1))), freeWavelengths);
					System.out.println(tableRow.getWavelength());
					addPath(dst, tableRow);
				}*/
				
				if(path!=null && path.length>0) {
					optimalPath = pathGrade(pt.getPathFreeWavelengths(path), pt.getPathAvailableBandWidth(path));
				}
				if(optimalPath!=null && optimalPath.size()>0) {
					int thisDomain = pt.getDomainFromNode(this.id).getID();
					//pt.getInBorderNode(neighborDomainId, dst);
					WDMLink wdmLink = pt.getInterdomainLink(thisDomain, domainPath[1]);
					TableRow tableRow = new TableRow(domainPath, wdmLink.getDstId(),  optimalPath.get((int)(Math.random() * (optimalPath.size()))), optimalPath);
					System.out.println("Banda: " + tableRow.getFreeWavelengths());
					addPath(dst, tableRow);
				}
			}
		}
	}
	
	public Vector<Integer> pathGrade(int[] freeWl, int[] maxWlB) {
		float hopGrade = (float) 0.2;
		float wlGrade = (float) 0.3;
		float bwGrade = (float) 0.5;
		int minHops;
		Vector<Integer> resultGrade = new Vector<Integer>();
		Vector<HashMap<Integer, Float>> competitors = new Vector<HashMap<Integer,Float>>();
		Iterator<HashMap<Integer, Float>> cmpIter;
		Iterator<Entry<Integer, Float>> mpIter;
		
		if (freeWl.length == 0 && maxWlB.length == 0) {
			return null;
		}
		
		//obtendo o caminho com menos saltos e dando uma nota para ele.
		if(freeWl.length > maxWlB.length) {
			minHops = freeWl.length; 
		}
		else {
			minHops = maxWlB.length;
		}
		
		//implementação hops ainda en andamento
		
		for (int i = 0; i < freeWl.length; i++) {
			competitors.add(new HashMap<Integer, Float>());
			competitors.get(i).put(freeWl[i], wlGrade);
		}
		for (int i = 0; i < maxWlB.length; i++) {
			if (competitors.get(i).containsKey(maxWlB[i])) {
				float last = competitors.get(i).get(freeWl[i]);
				competitors.get(i).put(freeWl[i],last + bwGrade);
			}
			else {
				competitors.get(i).put(freeWl[i], bwGrade);
			}
		}
		///obtendo caminho
		cmpIter = competitors.iterator();
		while (cmpIter.hasNext()) {
			int winWaveLengt = -1;
			float maxGrade = -1;
			HashMap<Integer,Float> map = (HashMap<Integer,Float>) cmpIter.next();
			mpIter = map.entrySet().iterator();
			while (mpIter.hasNext()) {
				Entry<Integer, Float> entry = (Entry<Integer, Float>) mpIter.next();
				if (entry.getValue() > maxGrade) {
					winWaveLengt = entry.getKey();
					maxGrade = entry.getValue();
				}
			}
			resultGrade.add(winWaveLengt);
		}
		return resultGrade;
	}
	
	public void addPath(OXC dst, TableRow row) {
		//PhysicalTopology pt = PhysicalTopology.getInstance();
		multidomainTable.remove(dst);
		multidomainTable.put(dst, row);
	}

	/**
	 * @param generator
	 * @throws Exception 
	 */
	public void update(OBGPSpeaker obgpSpeaker) throws Exception {
		OBGPTracer trace = OBGPTracer.getTracerObject();
		trace.reportUpdateMessage();
		
		for(int i=0; i<pt.getNumNodes(); i++) {
			if(pt.getNode(i).getId()!=obgpSpeaker.getId() && pt.getNode(i).getId()!=this.id) {
				TableRow tr = obgpSpeaker.getRib().getTableRow(pt.getNode(i));
				if(tr!=null) {
					// se obgpSpeaker.tr passa por this
					if(Auxiliar.hasSequence(tr.getPath(), pt.getDomainFromNode(obgpSpeaker.getId()).getID(), pt.getDomainFromNode(this.id).getID())) {
						if(pt.belongsSameDomain(this.id, pt.getNode(i).getId())) {
							WeightedGraph graph = pt.getIntradomainWeightedGraph(pt.getDomainFromNode(this.id).getID());
							int[] p = new int[2]; p[0] = obgpSpeaker.getId(); p[1] = this.id;
							int[] fw1 = pt.getPathFreeWavelengths(p);
							int[] fw2 = pt.getPathFreeWavelengths(Dijkstra.getShortestPathF(graph, this.id, pt.getNode(i).getId()));
							tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection(fw1, fw2)));
							if(tr.freeWavelengths.size()>0)
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							
							else
								tr.wavelength = -1;
							
						} else {
							TableRow tableRow = this.getTableRow(pt.getNode(i));
							//WeightedGraph graph = pt.getDomainWeightedGraph();
							int p[] = new int[2]; p[0] = obgpSpeaker.getId() ; p[1] = this.id;
							int fw[] = pt.getPathFreeWavelengths(p);
							if(tableRow!=null) {
								int[] temp = new int[1];
								temp[0] = obgpSpeaker.getDomainID();
								tr.asPath = Auxiliar.concat2(temp, tableRow.asPath);
								tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection
										(Auxiliar.vector2Array(tableRow.freeWavelengths), fw)));
								if(tr.freeWavelengths.size()>0)
									tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
								else
									tr.wavelength = -1;
								
							} 
						}
					//para atualizacao intradomain
					} else if(pt.belongsSameDomain(this.id, obgpSpeaker.getId())) {
						TableRow tableRow = this.getTableRow(pt.getNode(i));
						if(Auxiliar.arrayEqual(tr.asPath, tableRow.asPath)) {
							if(pt.getOutBorderNode(pt.getDomainFromNode(this.id).getID(), tr.asPath[1]).getId()==this.id) {
								WeightedGraph graph = pt.getIntradomainWeightedGraph(pt.getDomainFromNode(this.id).getID());
								int[] fw = pt.getPathFreeWavelengths(Dijkstra.getShortestPathF(graph, obgpSpeaker.getId(), this.id));
								tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection
										(fw, Auxiliar.vector2Array(tableRow.freeWavelengths))));
								if(tr.freeWavelengths.size()>0)
									tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
								else
									tr.wavelength = -1;
							} 
							
						} else {
							if(tr.asPath.length>tableRow.asPath.length) {
								tr.asPath = tableRow.asPath;
								int[] p = new int[2]; p[0] = obgpSpeaker.getId(); p[1] = this.id;
								int[] fw = pt.getPathFreeWavelengths(p);
								tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection(fw, Auxiliar.vector2Array(tableRow.freeWavelengths))));
								tr.nextHop = tableRow.nextHop;
								if(tr.freeWavelengths.size()>0)
									tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
								else
									tr.wavelength = -1;
								
							} else if(tr.asPath.length==tableRow.asPath.length && tableRow.freeWavelengths.size()>tr.freeWavelengths.size()) {
								tr.asPath = tableRow.asPath;
								int[] p = new int[2]; p[0] = obgpSpeaker.getId(); p[1] = this.id;
								int[] fw = pt.getPathFreeWavelengths(p);
								tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection(fw, Auxiliar.vector2Array(tableRow.freeWavelengths))));
								tr.nextHop = tableRow.nextHop;
								if(tr.freeWavelengths.size()>0)
									tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
								else
									tr.wavelength = -1;
							}
						}
						
					// se obgpSpeaker deve rodar a route selection
					} else {
						int[] p = new int[2]; p[0] = obgpSpeaker.getId(); p[1] = this.id;
						int[] fw = pt.getPathFreeWavelengths(p);
						TableRow tableRow = this.getTableRow(pt.getNode(i));
						if(tr.asPath.length>tableRow.asPath.length+1) {
							int[] temp = new int[1];
							temp[0] = obgpSpeaker.getDomainID();
							tr.asPath = Auxiliar.concat2(temp, tableRow.asPath);
							tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection(fw, Auxiliar.vector2Array(tableRow.freeWavelengths))));
							tr.nextHop = this.id;
							if(tr.freeWavelengths.size()>0)
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							else
								tr.wavelength = -1;
							
						} else if(tr.asPath.length==tableRow.asPath.length+1 && 
								Auxiliar.intIntersection(Auxiliar.vector2Array(tableRow.freeWavelengths), fw).length>tr.freeWavelengths.size()) {
							int[] temp = new int[1];
							temp[0] = obgpSpeaker.getDomainID();
							tr.asPath = Auxiliar.concat2(temp, tableRow.asPath);
							tr.setFreeWavelengths(Auxiliar.array2Vector(Auxiliar.intIntersection(fw, Auxiliar.vector2Array(tableRow.freeWavelengths))));
							tr.nextHop = this.id;
							if(tr.freeWavelengths.size()>0)
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							else
								tr.wavelength = -1;
						}
					}
				}
			}
		}
	}

	public void update(Flow flow) throws Exception {
		/*
		 para cada tr
		 	se tr.path tem a sequencia link.src -> link.dst
		 		tr.freeWavelenghts.remove(wavelength)
		 		se tr.wavelength==wavelength
		 			escolhe outro wavelength para tr.wavelength
		 */
		Lightpath lightpath = flow.getPath().getLightpath(0);
		int numLinks = lightpath.getLinks().size();
		WDMLink link = lightpath.getLink(numLinks-1);
		int wavelength = lightpath.getWavelength();
		
		for(int i=0; i<pt.getNumNodes(); i++) {
			TableRow tr = multidomainTable.get(pt.getNode(i));
			if(tr!=null && 
					Auxiliar.hasSequence(tr.getPath(), pt.getDomainFromNode(link.getSrcId()).getID(), pt.getDomainFromNode(link.getDstId()).getID())) {
				tr.freeWavelengths.remove((Integer)wavelength);
				if(!tr.freeWavelengths.isEmpty()) {
					if(tr.wavelength==wavelength) {
						tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
					}
					
				} else {
					tr.wavelength = -1;
				}
				
			}/* else if(pt.belongsSameDomain(link.getSrcId(), link.getDstId())) {
				
			}*/
		}
	}
	
	public void update2(Flow flow, Lightpath lp) throws Exception {
		Lightpath lightpath = flow.getPath().getLightpath(0);
		int numLinks = lightpath.getLinks().size();
		WDMLink link = lightpath.getLink(numLinks-1);
		int wavelength = lightpath.getWavelength();
		
		int[] path = pt.getPathFromLightpath(lp);
		
		for(int j=0; j<path.length-1; j++) {
			
			OXC node = pt.getNode(path[j]);
			if(node instanceof OBGPSpeaker) {
				
				TableRow tableRow = ((OBGPSpeaker)node).getRib().multidomainTable.get(pt.getNode(flow.getDst()));
				int[] domainPath = tableRow.asPath;
		
				for(int i=0; i<pt.getNumNodes(); i++) {
					TableRow tr = multidomainTable.get(pt.getNode(i));
					/*if(tr!=null && 
							Auxiliar.hasSequence(tr.getPath(), pt.getDomainFromNode(link.getSrcId()).getID(), pt.getDomainFromNode(link.getDstId()).getID())) {
						tr.freeWavelengths.remove((Integer)wavelength);
						if(!tr.freeWavelengths.isEmpty()) {
							if(tr.wavelength==wavelength) {
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							}
							
						} else {
							tr.wavelength = -1;
						}
						
					} else*/ if(tr!=null && pt.belongsSameDomain(link.getSrcId(), link.getDstId()) && 
							tr.asPath[1]==domainPath[1]) {
						tr.freeWavelengths.remove((Integer)wavelength);
						if(!tr.freeWavelengths.isEmpty()) {
							if(tr.wavelength==wavelength) {
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							}
							
						} else {
							tr.wavelength = -1;
						}
					}
				}
			}
		}
	}
	
	public void update(Lightpath lp) throws Exception {
		int[] path = pt.getPathFromLightpath(lp);
		int wavelength = lp.getWavelength();
		
		for(int i=0; i<path.length-1; i++) {
			OXC node = pt.getNode(path[i]);
			if(node instanceof OBGPSpeaker) {
				
				WDMLink link = pt.getLink(path[i], path[i+1]);
				
				for(int j=0; j<pt.getNumNodes(); j++) {
					
					TableRow tr = ((OBGPSpeaker)node).getRib().getTableRow(pt.getNode(j));
					if(tr!=null &&
							Auxiliar.hasSequence(tr.getPath(), pt.getDomainFromNode(path[i]).getID(), pt.getDomainFromNode(path[path.length-1]).getID())) {
						tr.freeWavelengths.remove((Integer)wavelength);
						if(!tr.freeWavelengths.isEmpty()) {
							if(tr.wavelength==wavelength) {
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							}
							
						} else {
							tr.wavelength = -1;
						}
						
					} else if(tr!=null && pt.belongsSameDomain(link.getSrcId(), link.getDstId()) && 
							pt.getOutBorderNode(pt.getDomainFromNode(this.id).getID(), pt.getDomainFromNode(tr.nextHop).getID()).getId()==path[path.length-1]) {
						tr.freeWavelengths.remove((Integer)wavelength);
						if(!tr.freeWavelengths.isEmpty()) {
							if(tr.wavelength==wavelength) {
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							}
							
						} else {
							tr.wavelength = -1;
						}
					}
				}
			}
		}
	}
	
	public void set(Flow flow) throws Exception {
		int[] path = pt.getPathFromPath(flow.getPath());
		int[] domainPath = pt.getDomainPathFromPath(path);
		int wavelength = flow.getPath().getLightpath(0).getWavelength();
		
		for(int i=0; i<path.length-1; i++) {
			OXC node = pt.getNode(path[i]);
			if(node instanceof OBGPSpeaker) {
				int domainId = pt.getDomainFromNode(node.getId()).getID();
				int nextDomainIndex = Auxiliar.arrayIndex(domainPath, domainId)+1;
				WDMLink link = pt.getLink(path[i], path[i+1]);
				
				for(int j=0; j<pt.getNumNodes(); j++) {
					
					TableRow tr = ((OBGPSpeaker)node).getRib().multidomainTable.get(pt.getNode(j));
					if(tr!=null && nextDomainIndex<domainPath.length &&
							Auxiliar.hasSequence(tr.getPath(), pt.getDomainFromNode(path[i]).getID(), domainPath[nextDomainIndex])) {
							
							if(link.getOwn(wavelength)==-1) {
								tr.freeWavelengths.add(wavelength);
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							}
							
					}
				}
			}
		}
	}
	
	public void set2(Flow flow) throws Exception {
		int[] path = pt.getPathFromPath(flow.getPath());
		int[] domainPath = pt.getDomainPathFromPath(path);
		int wavelength = flow.getPath().getLightpath(0).getWavelength();
		
		for(int i=0; i<path.length-1; i++) {
			OXC node = pt.getNode(path[i]);
			if(node instanceof OBGPSpeaker) {
				int domainId = pt.getDomainFromNode(node.getId()).getID();
				int nextDomainIndex = Auxiliar.arrayIndex(domainPath, domainId)+1;
				WDMLink link = pt.getLink(path[i], path[i+1]);
				
				for(int j=0; j<pt.getNumNodes(); j++) {
					if(!pt.belongsSameDomain(pt.getNode(j).getId(), flow.getSrc())) {
						TableRow tr = ((OBGPSpeaker)node).getRib().multidomainTable.get(pt.getNode(j));
						if(tr!=null && nextDomainIndex<domainPath.length && 
							Auxiliar.hasSequence(tr.getPath(), pt.getDomainFromNode(path[i]).getID(), domainPath[nextDomainIndex])) {
							
							if(link.getOwn(wavelength)==-1) {
								tr.freeWavelengths.add(wavelength);
								tr.wavelength = tr.freeWavelengths.get((int)(Math.random() * ((tr.freeWavelengths.size()-1) + 1)));
							}
								
						}
					}
				}
			}
		}
	}
	
	public boolean hasRegister(OXC oxc) {
		if(multidomainTable.containsKey(oxc)) {
			return true;
			
		} else {
			return false;
		}
	}
}
